// scaup
// (c) 2010, Normen Müller, normen.mueller@gmail.com  
// $Id: Files.scala 148 2010-12-22 06:32:49Z normen.mueller $
package scaup.io

import java.io.{File, BufferedInputStream, FileInputStream, IOException}
import java.security.{MessageDigest, NoSuchAlgorithmException}

import scala.math.PartiallyOrdered

import scaup.PimpedType
import scaup.io.jna.JNA

/** @author  <a href="mailto:normen.mueller@googlemail.com">Normen M\u00FCller</a>
 */
sealed trait FileW extends PimpedType[File] with PartiallyOrdered[File] with JNA {
  import scaup.Scaup._

  lazy val f = value

  lazy val name = f.getName()

  lazy val label = {
    val lastDot = name.lastIndexOf('.')
    val lastDirSep = name.lastIndexOf(File.separatorChar)
    if (lastDot == -1)
      name.substring(lastDirSep + 1)
    else name.substring(lastDirSep + 1, lastDot)
  }

  lazy val ext = {
    val lastDot = name.lastIndexOf('.')
    val lastDirSep = name.lastIndexOf(File.separatorChar)
    if (!((lastDot == -1 || lastDirSep != -1 && lastDot < lastDirSep)))
      Some(name.substring(lastDot + 1).toLowerCase())
    else None
  }

  lazy val p = path
  lazy val path = f.getPath()

  lazy val ap = absolutePath
  lazy val absolutePath = f.getAbsolutePath()

  lazy val cp = canonicalPath
  lazy val canonicalPath = f.getCanonicalPath()

  lazy val pp = parentPath
  lazy val parentPath = f.getParent()

  lazy val af = absoluteFile
  lazy val absoluteFile = f.getAbsoluteFile

  lazy val cf = canonicalFile
  lazy val canonicalFile = f.getCanonicalFile()

  lazy val pf = parentFile
  lazy val parentFile = f.getParentFile()

  def /(child: String) = new File(f, child)

  private lazy val sharedMemory = try { new com.sun.jna.Memory(1024) } catch { case _ => null }

  private lazy val fileModeOffset =
    if (OS.isLinux && OS.is64Bit)
      24
    else if (OS.isLinux && OS.is32Bit)
      16
    else if (OS.isOSX)
      8
    else 16

  lazy val typ: FileType.Value =
    if ((sharedMemory != null) && (cLibrary != null))
      sharedMemory synchronized {
        sharedMemory.clear
        val rc = cLibrary synchronized {
          cLibrary.lstat(f.ap, sharedMemory)
        }
        if (rc < 0)
          if (f.exists || f.isDirectory || f.isFile)
            FileType.UNKNOWN
          else FileType.NONE
        else {
          val mode: Int = sharedMemory.getShort(fileModeOffset)
          val t = mode & 0170000
          if (t == 0120000)
            FileType.SYMLINK
          else if (t == 0040000)
            FileType.DIRECTORY
          else if (t == 0100000)
            FileType.FILE
          else if (f.exists || f.isDirectory || f.isFile)
            FileType.UNKNOWN
          else FileType.NONE
        }
      }
    else FileType.UNKNOWN

  def toMD5 =
    if (f.isDirectory || !f.exists)
      None
    else try {
      val md = MessageDigest.getInstance("MD5")
      for(line <- io.Source.fromFile(f).getLines)        
        md.update(line.getBytes)
      Some(md.digest().map(0xFF & _).map{ "%02x".format(_) }.mkString)
    } catch {
      case ex@_ => error(ex.getMessage)
    }

  def diff(that: File) = scaup.text.Differ.diff(f, that)

  /** Create a symbolic link to <code>target</code> target.
   * 
   *  @return the symbolic link wrapped in an option if succeeded, otherwise none
   */
  def linkTo(target: String): Option[File] =
    if (f.exists || OS.isWindows)
      None
    else if (target != null && sharedMemory != null) {
      if (cLibrary != null)
        cLibrary synchronized {
          if (cLibrary.symlink(target, path) < 0)
            None
          else Some(f)
        }
      else None
    } else None

  lazy val linkPath: Option[String] = 
    if ((!OS.isWindows) && (sharedMemory != null) && (cLibrary != null))
      sharedMemory synchronized {
        sharedMemory.clear
        val rc = cLibrary synchronized {
          cLibrary.readlink(path, sharedMemory, 1024)
        }
        if (rc > 0) {
          val buffer = new Array[Byte](rc)
          sharedMemory.read(0, buffer, 0, rc)
          Some(new String(buffer, 0, rc))
        } else None
      }
    else None

  lazy val toList = (path.stripPrefix(File.separator) split File.separatorChar).toList

  lazy val toIndexedList = toList.zipWithIndex

  def ls(implicit recursive: Boolean = false): List[File] = 
    if (f.exists & f.isDirectory)
      if(recursive)
        ((Nil: List[File]) /: (for (d <- f.listFiles.toList) yield d :: d.ls))((l, d) => l ::: d)
      else f.listFiles.toList
    else Nil
  
  lazy val content = try {
    (new StringBuilder /: scala.io.Source.fromFile(f))(_ += _).toString
  } catch {
    case e: java.io.FileNotFoundException => error(e.getMessage)  
  }
  
  def touch = {
    if (!pf.exists) pf.mkdirs else ()
    if (f.createNewFile) Some(f) else None
  }

  def remove: Boolean = f.isDirectory match {
    case true => (true /: f.ls)((state, file) => state && file.remove)
    case false => f.delete
  }

  def tryCompareTo[B >: File <% PartiallyOrdered[B]](that: B): Option[Int] = that match {
    case that: File =>
      if (toIndexedList sameElements that.toIndexedList)
        Some(0)
      else if ((toIndexedList diff that.toIndexedList).isEmpty)
        Some(1)
      else if ((toIndexedList diff that.toIndexedList).isEmpty)
        Some(-1)
      else None
    case _ => None
  }
}

/** Common file types.
 * 
 *  @author  <a href="mailto:normen.mueller@googlemail.com">Normen M\u00FCller</a>
 */
object FileType extends Enumeration {
  val UNKNOWN = Value("unknown")
  val NONE = Value("none")
  val FILE = Value("file")
  val SYMLINK = Value("symlink")
  val DIRECTORY = Value("directory")
}

/** @author  <a href="mailto:normen.mueller@googlemail.com">Normen M\u00FCller</a>
 */
trait Files {
  implicit def FileTo(ss: File): FileW = new FileW {
    val value = ss
  }
}
